using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using ContentOverContainer.Common;
using Roslyn.Compilers;
using Roslyn.Compilers.CSharp;
using Roslyn.Compilers.Common;
using Roslyn.Services;
using Roslyn.Services.Formatting;

namespace ContentOverContainer
{



    /// <summary>
    /// Syntax rewriter transforming code containing multiplicity attributes to code without those attributes by applying the multiplicity types.
    /// This rewriter transforms syntax nodes annotated by the multiplicity attribute and build a list/stack with their reference to be adjusted too (see: ReferencesRewriter).
    /// It does not regard linq or lambda expression.
    /// </summary>
    /// <author>Harald Binkle</author>
    public class SimpleSyntaxMultiplicityRewriter : SyntaxRewriter
    {
        private readonly ReferencesList m_DocumentReferences = new ReferencesList();

        public ISemanticModel SemModel { get; set; }

        public ISolution Solut { get; set; }

        private SimpleSyntaxMultiplicityRewriter(ISolution pSol, ISemanticModel pSem)
        {
            SemModel = pSem;
            Solut = pSol;
        }


        public static ISolution Visit(ISolution pSolution)
        {
            ISolution newSolution = pSolution;
            var references = new ReferencesList();

            newSolution = ProcessAttributes(newSolution, references);

            newSolution = ProcessReferences(newSolution, references);

            return newSolution;
        }

        private static ISolution ProcessReferences(ISolution pSolution, ReferencesList references)
        {
            foreach (var project in pSolution.Projects)
            {
                foreach (var document in project.Documents)
                {
                    var refRewrite = new ReferencesRewriter(pSolution, references);
                    var newChangedRefRootnode = refRewrite.Visit(document.GetSyntaxRoot() as SyntaxNode);
                    IText newText = newChangedRefRootnode.GetText();
                    pSolution = pSolution.UpdateDocument(document.Id, newText);
                }
            }
            return pSolution;
        }

        private static ISolution ProcessAttributes(ISolution pSolution,ReferencesList references)
        {
            foreach (var project in pSolution.Projects)
            {
                foreach (var document in project.Documents)
                {
                    var sem = document.GetSemanticModel();
                    var rewrite = new SimpleSyntaxMultiplicityRewriter(pSolution, sem);
                    var newRootnode = rewrite.Visit(document.GetSyntaxRoot() as SyntaxNode).Format(FormattingOptions.GetDefaultOptions()).GetFormattedRoot();
                    
                    references.Add(rewrite.m_DocumentReferences);
                   
                    //foreach (var refNode in rewrite.m_DocumentReferences)
                    //{
                    //    var newNode = (SyntaxNode)refNode.Key.WithAdditionalAnnotations(new ReferenceAnnotation(refNode.Value));
                    //    newRootnode = newRootnode.ReplaceNode((SyntaxNode)refNode.Key, newNode);
                    //}

                    var anno = newRootnode.GetAnnotatedNodesAndTokens(typeof (ReferenceAnnotation));
                    var count = anno.Count();
                    //assign the new root to the document for further processing
                    IText newText = newRootnode.GetText();
                    pSolution = pSolution.UpdateDocument(document.Id, newText);
                }
            }
            return pSolution;
        }

        private static void ProcessReferences(ReferencesList pReferencesStack)
        {
            foreach (var refPair in pReferencesStack)
            {
                ChangeType(refPair, pReferencesStack);
            }
            
        }

        private static void ChangeType(NodeTypeInfo pNodeToChange, ReferencesList pStack)
        {
            var parent = pNodeToChange.Node.Parent;
            var kind = (SyntaxKind)parent.Kind;
            switch (kind)
            {
                case SyntaxKind.ParenthesizedExpression:
                    break;
                case SyntaxKind.ConditionalExpression:
                    break;
                case SyntaxKind.InvocationExpression:
                    break;
                case SyntaxKind.ElementAccessExpression:
                    break;
                case SyntaxKind.ArgumentList:
                    break;
                case SyntaxKind.BracketedArgumentList:
                    break;
                case SyntaxKind.Argument:
                    //TODO: pNodeToChange.Key.Parent.Parent.ToString()   (Method)                    
                    break;
                case SyntaxKind.CastExpression:
                    break;
                case SyntaxKind.AnonymousMethodExpression:
                    break;
                case SyntaxKind.SimpleLambdaExpression:
                    break;
                case SyntaxKind.ParenthesizedLambdaExpression:
                    break;
                case SyntaxKind.ObjectInitializerExpression:
                    break;
                case SyntaxKind.CollectionInitializerExpression:
                    break;
                case SyntaxKind.ArrayInitializerExpression:
                    break;
                case SyntaxKind.AnonymousObjectMemberDeclarator:
                    break;
                case SyntaxKind.ComplexElementInitializerExpression:
                    break;
                case SyntaxKind.ObjectCreationExpression:
                    break;
                case SyntaxKind.AnonymousObjectCreationExpression:
                    break;
                case SyntaxKind.ArrayCreationExpression:
                    break;
                case SyntaxKind.ImplicitArrayCreationExpression:
                    break;
                case SyntaxKind.StackAllocArrayCreationExpression:
                    break;
                case SyntaxKind.OmittedArraySizeExpression:
                    break;
                case SyntaxKind.AddExpression:
                case SyntaxKind.SubtractExpression:
                case SyntaxKind.MultiplyExpression:
                case SyntaxKind.DivideExpression:
                case SyntaxKind.ModuloExpression:
                case SyntaxKind.LeftShiftExpression:
                case SyntaxKind.RightShiftExpression:
                case SyntaxKind.LogicalOrExpression:
                case SyntaxKind.LogicalAndExpression:
                case SyntaxKind.BitwiseOrExpression:
                case SyntaxKind.BitwiseAndExpression:
                case SyntaxKind.ExclusiveOrExpression:
                case SyntaxKind.EqualsExpression:
                case SyntaxKind.NotEqualsExpression:
                case SyntaxKind.LessThanExpression:
                case SyntaxKind.LessThanOrEqualExpression:
                case SyntaxKind.GreaterThanExpression:
                case SyntaxKind.GreaterThanOrEqualExpression:
                    //TODO: mathematical expression: "m_Job + EmailAdress.ToString()"
                    break;
                case SyntaxKind.IsExpression:
                    break;
                case SyntaxKind.AsExpression:
                    break;
                case SyntaxKind.CoalesceExpression:
                    break;
                case SyntaxKind.MemberAccessExpression:
                    //TODO: pNodeToChange.Key.Parent.Parent.ToString(); //"= m_Job.Substring(0, 3)"
                    break;
                case SyntaxKind.PointerMemberAccessExpression:
                    break;
                case SyntaxKind.AssignExpression:
                    //TODO: m_Job = "new value"
                    ExpressionSyntaxConverter.ConvertAssignExpression((BinaryExpressionSyntax)parent);
                    break;
                case SyntaxKind.AddAssignExpression:
                    break;
                case SyntaxKind.SubtractAssignExpression:
                    break;
                case SyntaxKind.MultiplyAssignExpression:
                    break;
                case SyntaxKind.DivideAssignExpression:
                    break;
                case SyntaxKind.ModuloAssignExpression:
                    break;
                case SyntaxKind.AndAssignExpression:
                    break;
                case SyntaxKind.ExclusiveOrAssignExpression:
                    break;
                case SyntaxKind.OrAssignExpression:
                    break;
                case SyntaxKind.LeftShiftAssignExpression:
                    break;
                case SyntaxKind.RightShiftAssignExpression:
                    break;
                case SyntaxKind.PlusExpression:
                    break;
                case SyntaxKind.NegateExpression:
                    break;
                case SyntaxKind.BitwiseNotExpression:
                    break;
                case SyntaxKind.LogicalNotExpression:
                    break;
                case SyntaxKind.PreIncrementExpression:
                    break;
                case SyntaxKind.PreDecrementExpression:
                    break;
                case SyntaxKind.PointerIndirectionExpression:
                    break;
                case SyntaxKind.AddressOfExpression:
                    break;
                case SyntaxKind.PostIncrementExpression:
                    break;
                case SyntaxKind.PostDecrementExpression:
                    break;
                case SyntaxKind.ThisExpression:
                    break;
                case SyntaxKind.BaseExpression:
                    break;
                case SyntaxKind.ArgListExpression:
                    break;
                case SyntaxKind.NumericLiteralExpression:
                    break;
                case SyntaxKind.StringLiteralExpression:
                    break;
                case SyntaxKind.CharacterLiteralExpression:
                    break;
                case SyntaxKind.TrueLiteralExpression:
                    break;
                case SyntaxKind.FalseLiteralExpression:
                    break;
                case SyntaxKind.NullLiteralExpression:
                    break;
                case SyntaxKind.TypeOfExpression:
                    break;
                case SyntaxKind.SizeOfExpression:
                    break;
                case SyntaxKind.CheckedExpression:
                    break;
                case SyntaxKind.UncheckedExpression:
                    break;
                case SyntaxKind.DefaultExpression:
                    break;
                case SyntaxKind.MakeRefExpression:
                    break;
                case SyntaxKind.RefValueExpression:
                    break;
                case SyntaxKind.RefTypeExpression:
                    break;
                    //Linq cases
                    //case SyntaxKind.QueryExpression:
                    //    break;
                    //case SyntaxKind.QueryBody:
                    //    break;
                    //case SyntaxKind.FromClause:
                    //    break;
                    //case SyntaxKind.LetClause:
                    //    break;
                    //case SyntaxKind.JoinClause:
                    //    break;
                    //case SyntaxKind.JoinIntoClause:
                    //    break;
                    //case SyntaxKind.WhereClause:
                    //    break;
                    //case SyntaxKind.OrderByClause:
                    //    break;
                    //case SyntaxKind.AscendingOrdering:
                    //    break;
                    //case SyntaxKind.DescendingOrdering:
                    //    break;
                    //case SyntaxKind.SelectClause:
                    //    break;
                    //case SyntaxKind.GroupClause:
                    //    break;
                    //case SyntaxKind.QueryContinuation:
                    break;
                case SyntaxKind.Block:
                    break;
                case SyntaxKind.LocalDeclarationStatement:
                    break;
                case SyntaxKind.VariableDeclaration:
                    break;
                case SyntaxKind.VariableDeclarator:
                    break;
                case SyntaxKind.EqualsValueClause:
                    break;
                case SyntaxKind.ExpressionStatement:
                    break;
                case SyntaxKind.EmptyStatement:
                    break;
                case SyntaxKind.LabeledStatement:
                    break;
                case SyntaxKind.GotoStatement:
                    break;
                case SyntaxKind.GotoCaseStatement:
                    break;
                case SyntaxKind.GotoDefaultStatement:
                    break;
                case SyntaxKind.BreakStatement:
                    break;
                case SyntaxKind.ContinueStatement:
                    break;
                case SyntaxKind.ReturnStatement:
                    //TODO: return m_ContainerType;  --> change method return type
                    break;
                case SyntaxKind.YieldReturnStatement:
                    break;
                case SyntaxKind.YieldBreakStatement:
                    break;
                case SyntaxKind.ThrowStatement:
                    break;
                case SyntaxKind.WhileStatement:
                    break;
                case SyntaxKind.DoStatement:
                    break;
                case SyntaxKind.ForStatement:
                    break;
                case SyntaxKind.ForEachStatement:
                    break;
                case SyntaxKind.UsingStatement:
                    break;
                case SyntaxKind.FixedStatement:
                    break;
                case SyntaxKind.CheckedStatement:
                    break;
                case SyntaxKind.UncheckedStatement:
                    break;
                case SyntaxKind.UnsafeStatement:
                    break;
                case SyntaxKind.LockStatement:
                    break;
                case SyntaxKind.IfStatement:
                    break;
                case SyntaxKind.ElseClause:
                    break;
                case SyntaxKind.SwitchStatement:
                    break;
                case SyntaxKind.SwitchSection:
                    break;
                case SyntaxKind.CaseSwitchLabel:
                    break;
                case SyntaxKind.DefaultSwitchLabel:
                    break;
                case SyntaxKind.TryStatement:
                    break;
                case SyntaxKind.CatchClause:
                    break;
                case SyntaxKind.CatchDeclaration:
                    break;
                case SyntaxKind.FinallyClause:
                    break;
                case SyntaxKind.CompilationUnit:
                    break;
                case SyntaxKind.GlobalStatement:
                    break;
                case SyntaxKind.NamespaceDeclaration:
                    break;
                case SyntaxKind.UsingDirective:
                    break;
                case SyntaxKind.ExternAliasDirective:
                    break;
                case SyntaxKind.AttributeList:
                    break;
                case SyntaxKind.AttributeTargetSpecifier:
                    break;
                case SyntaxKind.Attribute:
                    break;
                case SyntaxKind.AttributeArgumentList:
                    break;
                case SyntaxKind.AttributeArgument:
                    break;
                case SyntaxKind.NameEquals:
                    break;
                case SyntaxKind.ClassDeclaration:
                    break;
                case SyntaxKind.StructDeclaration:
                    break;
                case SyntaxKind.InterfaceDeclaration:
                    break;
                case SyntaxKind.EnumDeclaration:
                    break;
                case SyntaxKind.DelegateDeclaration:
                    break;
                case SyntaxKind.BaseList:
                    break;
                case SyntaxKind.TypeParameterConstraintClause:
                    break;
                case SyntaxKind.ConstructorConstraint:
                    break;
                case SyntaxKind.ClassConstraint:
                    break;
                case SyntaxKind.StructConstraint:
                    break;
                case SyntaxKind.TypeConstraint:
                    break;
                case SyntaxKind.ExplicitInterfaceSpecifier:
                    break;
                case SyntaxKind.EnumMemberDeclaration:
                    break;
                case SyntaxKind.MethodDeclaration:
                    break;
                case SyntaxKind.OperatorDeclaration:
                    break;
                case SyntaxKind.ConversionOperatorDeclaration:
                    break;
                case SyntaxKind.ConstructorDeclaration:
                    break;
                case SyntaxKind.BaseConstructorInitializer:
                    break;
                case SyntaxKind.ThisConstructorInitializer:
                    break;
                case SyntaxKind.ParameterList:
                    break;
                case SyntaxKind.BracketedParameterList:
                    break;
                case SyntaxKind.Parameter:
                    break;
                case SyntaxKind.TypeParameterList:
                    break;
                case SyntaxKind.TypeParameter:
                    break;
                case SyntaxKind.IncompleteMember:
                    break;
                default:
                    throw new ArgumentException("Unkown syntax type.");
            }


        }



        public override SyntaxNode VisitFieldDeclaration(FieldDeclarationSyntax node)
        {
            FieldDeclarationSyntax syntaxNode = base.VisitFieldDeclaration(node) as FieldDeclarationSyntax;
            if (syntaxNode == null) return null;

            var multiplicityAtt = node.AttributeLists.GetMultiplicityAttributes();
            if (multiplicityAtt == null)
                return syntaxNode;

            var varDecl = node.Declaration;

            var type = multiplicityAtt.GetMultiplicityType();
            var kind = multiplicityAtt.GetMultiplicityKind();

            var references = varDecl.FindAllReferences(this.SemModel, this.Solut).Cast<SyntaxNode>();
            

            m_DocumentReferences.Add(references, type);

            var newnode = node.ChangeToMultiplicityType(kind, type);
            var returnNode = newnode.RemoveMultiplicityAttribute(newnode.AttributeLists, true);
            return returnNode;
        }

        public override SyntaxNode VisitPropertyDeclaration(PropertyDeclarationSyntax node)
        {
            PropertyDeclarationSyntax syntaxNode = base.VisitPropertyDeclaration(node) as PropertyDeclarationSyntax;
            if (syntaxNode == null) return null;

            var multiplicityAtt = node.AttributeLists.GetMultiplicityAttributes();
            if (multiplicityAtt == null)
                return syntaxNode;

            var type = multiplicityAtt.GetMultiplicityType();
            var kind = multiplicityAtt.GetMultiplicityKind();

            var references = node.FindAllReferences(this.SemModel, this.Solut).Cast<SyntaxNode>();
            m_DocumentReferences.Add(references, type);

            var newnode = node;
            if (type != null)
                newnode = node.ChangeToMultiplicityType(type);
            var returnNode = newnode.RemoveMultiplicityAttribute(newnode.AttributeLists, true);
            return returnNode;
        }














        //public override SyntaxNode VisitPropertyDeclaration(PropertyDeclarationSyntax node)
        //{
        //    SyntaxNode syntaxNode = base.VisitPropertyDeclaration(node);
        //    return syntaxNode;
        //    //TODO: ...

        //    if (!HasMultiplicityAttribute(node))//any other attrbiute
        //    {
        //        return syntaxNode;
        //    }

        //    //see if MultiplicityType is "any" and replace the type of the property by the the container type
        //    //transition rule (R-MDECL)     

        //    SyntaxNode nextNode = GetNextNode(syntaxNode);
        //    if (nextNode == null)
        //        return syntaxNode;

        //    var references = FindAllReferences(syntaxNode);

        //    return syntaxNode;
        //}

        //public override SyntaxNode VisitAttribute(AttributeSyntax node)
        //{
        //    SyntaxNode syntaxNode =  base.VisitAttribute(node);

        //    if (!((IdentifierNameSyntax)node.Name).Identifier.ValueText.Equals("Multiplicity"))
        //    {
        //        return syntaxNode;
        //    }


        //    //syntaxNode.Parent = AttributeList --> get the parent of the list
        //    var multiNode = syntaxNode.Parent.Parent;
        //    //var refs = FindAllReferences(multiNode);
        //    return syntaxNode.Parent.RemoveNode(syntaxNode, SyntaxRemoveOptions.KeepExteriorTrivia);

        //}

        //private static bool HasMultiplicityAttribute(MemberDeclarationSyntax node)        
        //{
        //    return node.AttributeLists.SelectMany(a => a.ChildNodes().OfType<AttributeSyntax>())
        //                                         .Any(a => ((IdentifierNameSyntax)(a.Name)).Identifier.ValueText.Equals("Multiplicity"));
        //}





    }
}
